//-------------------------------------------------------------------
//	Copyright (c) 2010-2011  Zhirnov Andrey
//	This file is part of the "UXGEN_ResourceConverter" project.
//	See copyright notice in "Converter.h".
//-------------------------------------------------------------------

#include "CSoundConverter.h"
#include "..\\core\\CConverter.h"

static const char	*a_codec_names[] = { "none", "PCM linear", "PCM 8", "PCM 16", "ULaw", "ALaw" };


CSoundConverter::CSoundConverter(): _eFormat(e_sound_format::UNKNOWN)
{
}


CSoundConverter::~CSoundConverter()
{
	_aSoundData.Clear();
}


HRESULT CALLBACK CSoundConverter::GetSupportedFormats(char *pBuffer, uint uCount)
{
	static const string		s_sFormats = string("sounds|") << "*.pcm;" << "*.wav;" << "*.ogg";

	if ( pBuffer == nullptr || uCount == 0 )
		return E_INVALIDARG;

	if ( uCount > s_sFormats.Length() )
	{
		memcpy( pBuffer, s_sFormats.cstr(), s_sFormats.Size() );
		return S_OK;
	}
	return S_FALSE;
}


HRESULT CALLBACK CSoundConverter::Load(const char *pcFileName)
{
	if ( pcFileName == nullptr )
		return E_INVALIDARG;

	bool		ret		= false;
	const char	*p_ext	= String::GetFileExt( pcFileName );
	IFilePtr	p_file	= nullptr;

	if ( CORE->LoadFile( pcFileName, p_file, e_file_open_flags::DEF_READ ) != S_OK )
		return E_FAIL;

	if ( _stricmp( p_ext, "pcm" ) == 0 )		ret = _LoadPCM( p_file );	else
	if ( _stricmp( p_ext, "wav" ) == 0 )		ret = _LoadWAV( p_file );	else
	if ( _stricmp( p_ext, "ogg" ) == 0 )		ret = _LoadOGG( p_file );	else
	//if ( stricmp( p_ext, "mp3" ) == 0 )		ret = _LoadMP3( p_file );
	{
		LOG( "Unknown file format.", );
		ret = false;
	}

	return ret ? S_OK : E_FAIL;
}


HRESULT CALLBACK CSoundConverter::Save(const char *pcFileName)
{
	if ( pcFileName == nullptr )
		return E_INVALIDARG;

	bool		ret		= false;
	IFilePtr	p_file	= nullptr;

	if ( CORE->LoadFile( pcFileName, p_file, e_file_open_flags::DEF_WRITE ) != S_OK )
		return E_FAIL;

	ret = _SaveSND( p_file );

	return ret ? S_OK : E_FAIL;
}


HRESULT CALLBACK CSoundConverter::Delete()
{
	//_sParams = TSoundParams();
	_sInfo   = TSoundSrcInfo();
	_eFormat = e_sound_format::UNKNOWN;
	_aSoundData.Clear();
	return S_OK;
}


HRESULT CALLBACK CSoundConverter::SetParams(const TSoundParams &sParams)
{
	_sParams = sParams;
	return S_OK;
}


HRESULT CALLBACK CSoundConverter::GetParams(TSoundParams &sParams)
{
	sParams = _sParams;
	return S_OK;
}


HRESULT CALLBACK CSoundConverter::GetInfo(char *pBuffer, uint uCount)
{
	if ( pBuffer == nullptr || uCount == 0 )
		return E_INVALIDARG;

	string	str;

	str << "Codec:          " << _sInfo.pCodec << "\x00D\x00A";
	str << "Channels:       " << _sInfo.uChannels << "\x00D\x00A";
	str << "Bitrate:        " << _sInfo.uBitrate << "\x00D\x00A";
	str << "Frequency:      " << _sInfo.uFrequency << "\x00D\x00A";
	str << "Bit per sample: " << _sInfo.uBitPerSample << "\x00D\x00A";
	str << "Data size:      " << (_sInfo.uSize >> 10) << " kb";
	//str << "Time:			" << 

	if ( uCount > str.Length() )
	{
		memcpy( pBuffer, str.cstr(), str.Size() );
		return S_OK;
	}

	return S_FALSE;
}
	

HRESULT CALLBACK CSoundConverter::GetType(E_SUBSYSTEM_TYPE &eType)
{
	eType = SST_SOUND_CONVERTER;
	return S_OK;
}


bool CSoundConverter::_LoadPCM(IFilePtr pFile)
{
	return false;
}


inline bool ReadInt32LE(IFilePtr pFile, int32 &iVal)
{
	uint8	buf[4];
	bool	ret = pFile->ReadS( buf, sizeof(buf) );
	
	iVal =	((int32) buf[3] << 24)	|
			((int32) buf[2] << 16)	|
			((int32) buf[1] << 8)	|
			((int32) buf[0] );

	return ret;
}


inline bool ReadUInt32LE(IFilePtr pFile, uint32 &uVal)
{
	return ReadInt32LE( pFile, (int32 &)uVal );
}


inline bool ReadInt16LE(IFilePtr pFile, int16 &iVal)
{
	uint8	buf[2];
	bool	ret = pFile->ReadS( buf, sizeof(buf) );
	
	iVal =	((int32) buf[1] << 8)	|
			((int32) buf[0] );

	return ret;
}


inline bool ReadUInt16LE(IFilePtr pFile, uint16 &uVal)
{
	return ReadInt16LE( pFile, (int16 &)uVal );
}


inline bool ReadInt32BE(IFilePtr pFile, int32 &iVal)
{
	uint8	buf[4];
	bool	ret = pFile->ReadS( buf, sizeof(buf) );

	iVal =	((int32) buf[0] << 24)	|
			((int32) buf[1] << 16)	|
			((int32) buf[2] << 8)	|
			((int32) buf[3] );

	return ret;
}


inline bool ReadInt16BE(IFilePtr pFile, int16 &iVal)
{
	uint8	buf[2];
	bool	ret = pFile->ReadS( buf, sizeof(buf) );
	
	iVal =	((int32) buf[0] << 8)	|
			((int32) buf[1] );

	return ret;
}


typedef enum
{
	LittleEndian,
	BigEndian,
	UnknwonEndian
} Endianess;


static Endianess endianess(void)
{
	union
	{
		long l;
		char c[sizeof (long)];
	} u;

	u.l = 1;
	return (u.c[0] == 1) ? LittleEndian : ((u.c[sizeof (long) - 1] == 1) ? BigEndian : UnknwonEndian);
}


bool CSoundConverter::_LoadWAV(IFilePtr pFile)	// 4c c9 0a 00		= 3 6c 17 dd
{
	bool		found_header = false;
	uint32		chunkLength;
	int32		magic;
	uint16		audioFormat;
	uint16		numChannels;
	uint32		sampleFrequency;
	uint32		byteRate;
	uint16		blockAlign;
	uint16		bitsPerSample;

	pFile->SeekS( 4, e_seek::SET );

	if ( !ReadUInt32LE( pFile, chunkLength ) || !ReadInt32BE( pFile, magic ) ) {
		LOG( "error 1", );
		return false;
	}

	if ( magic != 0x57415645 ) {      // "WAVE"
		LOG( (string("File is not WAVE file, magic: ") << magic).cstr(), );
		return false;
	}

	while (1)
	{
		if ( !ReadInt32BE( pFile, magic ) || !ReadUInt32LE( pFile, chunkLength ) ) {
			LOG( "error 2", );
			return false;
		}

		if ( magic == 0x666d7420 )  // "fmt "
		{
			found_header = true;

			if ( chunkLength < 16 ) {
				LOG( "error 5", );
				return false;
			}

			if ( !ReadUInt16LE( pFile, audioFormat )		||
				 !ReadUInt16LE( pFile, numChannels )		||
				 !ReadUInt32LE( pFile, sampleFrequency )	||
				 !ReadUInt32LE( pFile, byteRate )		||
				 !ReadUInt16LE( pFile, blockAlign )		||
				 !ReadUInt16LE( pFile, bitsPerSample ) )
			{
				LOG( "error 4", );
				return false;
			}

			if ( !pFile->SeekS( chunkLength - 16, e_seek::CURRENT ) ) {
				LOG( "Can't seek.", );	// debug
				return false;
			}
			
			switch ( audioFormat )
			{
				case 1:            // PCM
					_eFormat = ( bitsPerSample == 8 || endianess() == LittleEndian ) ?
								e_sound_format::PCM_LINEAR : e_sound_format::PCM_16;
					break;
				case 7:            // uLaw
					bitsPerSample *= 2;
					_eFormat = e_sound_format::ULAW_8;
					break;
				default:
					LOG( "Wrong audio format.", );
					return false;
			}
		}
		else
		if ( magic == 0x64617461 )     // "data"
		{
			if ( !found_header ) {
				LOG( "Header not found.", );
				return false;
			}

			_aSoundData.Resize( chunkLength, false );
			pFile->ReadS( _aSoundData.ptr(), _aSoundData.Size() );

			_sInfo.uBitrate		 = byteRate;
			_sInfo.uChannels	 = numChannels;
			_sInfo.uFrequency	 = sampleFrequency;
			_sInfo.uBitPerSample = bitsPerSample;
			_sInfo.uSize		 = _aSoundData.Size();
			_sInfo.pCodec		 = a_codec_names[_eFormat];

			return true;
		}
		else
		{
			if ( !pFile->SeekS( chunkLength, e_seek::CURRENT ) ) {
				LOG( "Can't seek.", );	// debug
				return false;
			}
		}

		bool	b_eof = pFile->IsEOF();

		if ( (chunkLength & 1) && !b_eof && !pFile->SeekS( 1, e_seek::CURRENT ) ) {
			LOG( "error 6", );
			return false;
		}
	}
	LOG( "error 7", );
	return false;
}


bool CSoundConverter::_LoadOGG(IFilePtr pFile)
{
	return false;
}


bool CSoundConverter::_SaveSND(IFilePtr pFile)
{
	TStreamedSource		s_sound;

	s_sound.eIntFormat	= _eFormat;
	s_sound.uFreq		= _sInfo.uFrequency;
	s_sound.bLooping	= _sParams.bLooping;
	s_sound.fGain		= _sParams.fGain;
	s_sound.fPitch		= _sParams.fPitch;

	if ( !Sound::GetFormat( s_sound.eFormat, _sInfo.uChannels, _sInfo.uBitPerSample ) ) {
		LOG( "Can't shoose sound format.", );
		return false;
	}

	if ( _sParams.uBlocksCount > 1 )
	{		
		uint		u_size = 0;
		const uint	u_temp = _aSoundData.Size() / (_sParams.uBlocksCount - 1);

		for (uint i = 0; i < _sParams.uBlocksCount-1; ++i)
		{
			s_sound.aSizes.PushBack( u_temp );
			u_size += u_temp;
		}
		s_sound.aSizes.PushBack( _aSoundData.Size() - u_size );
	}
	else
	{
		s_sound.aSizes.PushBack( _aSoundData.Size() );
	}
	
	s_sound.aData.SetMem( _aSoundData );

	bool	ret = Loaders::SaveSound( pFile, s_sound );

	_aSoundData.SetMem( s_sound.aData );
	return ret;
}